%%--- coding:utf-8 ---
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% File Name: mc_utils
%%% Created on : 2024/4/16 20:50
%%% @author Gaylen 252323463@qq.com
%%% @copyright (C) 2024, freedom
%%% @doc
%%%
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-module(mc_utils).
-author("Gaylen").
-include("mongodb_driver.hrl").

%% API
-export([
    get_value/2,
    get_value/3,
    set_value/3,
    is_proplist/1,
    random_nonce/1,
    encode_name/1,
    pw_hash/2,
    hmac/2,
    get_timeout/0,
    pw_key/3,

    dbcoll/2,
    update_dbcoll/2,

    value_to_binary/1,

    request_id/0
]).

-define(OLD_CRYPTO_API, true).
-ifdef(OTP_RELEASE).
-if(?OTP_RELEASE >= 23).
-undef(OLD_CRYPTO_API).
-endif.
-endif.


get_value(Key, List) ->
    get_value(Key, List, undefined).

get_value(Key, List, Default) ->
    case lists:keyfind(Key, 1, List) of
        {_, Value} -> Value;
        false -> Default
    end.

set_value(Key, Value, List) ->
    lists:keystore(Key, 1, List, {Key, Value}).

-spec is_proplist(list() | any()) -> boolean().
is_proplist(List) ->
    Check = fun({X, _}) when is_atom(X) -> true;(_) -> false end,
    lists:all(Check, List).

-spec random_nonce(integer()) -> binary().
random_nonce(TextLength) ->
    ByteLength = trunc(TextLength / 4 * 3),
    RandBytes = crypto:strong_rand_bytes(ByteLength),
    base64:encode(RandBytes).

-spec encode_name(binary()) -> binary().
encode_name(Name) ->
    Comma = re:replace(Name, <<"=">>, <<"=3D">>, [{return, binary}]),
    re:replace(Comma, <<",">>, <<"=2C">>, [{return, binary}]).

pw_hash(Username, Password) ->
    bson:utf8(binary_to_hexstr(crypto:hash(md5, [Username, <<":mongo:">>, Password]))).

%% @private
binary_to_hexstr(Bin) ->
    lists:flatten([io_lib:format("~2.16.0b", [X]) || X <- binary_to_list(Bin)]).

-ifdef(OLD_CRYPTO_API).
hmac(One, Two) -> crypto:hmac(sha, One, Two).
-else.
hmac(One, Two) -> crypto:mac(hmac, sha, One, Two).
-endif.

%% mongo阻塞读取的超时时间(毫秒)，定义环境变量：mc_worker_call_timeout
get_timeout() ->
    case application:get_env(mc_worker_call_timeout) of
        {ok, Time} -> Time;
        undefined -> infinity
    end.

pw_key(Nonce, Username, Password) ->
    bson:utf8(binary_to_hexstr(crypto:hash(md5, [Nonce, Username, pw_hash(Username, Password)]))).

%@doc Concat db and collection name with period (.) in between
-spec dbcoll(database(), colldb()) -> bson:utf8().
dbcoll(Db, {undefined, Coll}) ->
    dbcoll(Db, Coll);
dbcoll(_, {Db, Coll}) ->
    dbcoll(Db, Coll);
dbcoll(Db, Coll) ->
    <<(binarize(Db))/binary, $., (binarize(Coll))/binary>>.

-spec binarize(binary() | atom()) -> binary().
%@doc Ensures the given term is converted to a UTF-8 binary.
binarize(Term) when is_binary(Term) -> Term;
binarize(Term) when is_atom(Term) -> atom_to_binary(Term, utf8).

update_dbcoll({Db, _}, Coll) -> {Db, Coll};
update_dbcoll(_, Coll) -> Coll.

value_to_binary(Value) when is_integer(Value) ->
    bson:utf8(integer_to_list(Value));
value_to_binary(Value) when is_atom(Value) ->
    atom_to_binary(Value, utf8);
value_to_binary(Value) when is_binary(Value) ->
    Value;
value_to_binary(_Value) ->
    <<>>.

request_id() ->
    ets:update_counter(?ETS_MONGO_ID, requestid_counter, {2, 1, ?MONGO_MAX_INT32, 0}).
